///////////////////////////////////////////////////////////////////////////////
//
// Licensed under the MIT License
//
// Copyright (c) 2010 Chris Colinsky
// All Rights Reserved
//
// version	0.5
// http://code.google.com/p/sugarcookie/
// http://code.google.com/p/sugarcookie/wiki/License
//
////////////////////////////////////////////////////////////////////////////////

package sugarcookie.tween {
	import flash.events.EventDispatcher;

	public class ValueTween extends EventDispatcher {
		/**
		 * easeNone creates linear data between start and end values
		 *
		 * @param s number of steps in the tween
		 * @param b begin value of tween
		 * @param e end value of tween
		 */
		public static function easeNone (s:Number,b:Number,e:Number):Array {
			var c:Number = e-b;
			//store the values
			var v:Array = new Array();
			//iterate through the steps to create the tween values
			for (var i:Number=0;i<=s;i++) {
				v.push(c*i/s+b);
			}
			return v;
		}
		
		/**
		 * easeIn for all tween types
		 *
		 * @param s number of steps in the tween
		 * @param b begin value of tween
		 * @param e end value of tween
		 * @param type easing type to be use for easeIn calculation
		 * @param opt an object containing optional paramaters for back and elastic easing.  opt.os = overshoot (back), opt.a = amplitude (elastic), opt.p = period (elastic)
		 */
		public static function easeIn (s:Number,b:Number,e:Number,type:String,opt:Object):Array {
			if (type=="linear" && opt.type!="current") return easeNone(s,b,e);
			
			//define easing functions
			var funcs:Array = getFuncs("easeIn");
			
			if (opt.type=="current") return funcs[type.toLowerCase()](e-b,s)
			else return doIteration(s,b,e,type,opt,funcs[type.toLowerCase()]);
		}
		
		/**
		 * easeOut for all tween types
		 *
		 * @param s number of steps in the tween
		 * @param b begin value of tween
		 * @param e end value of tween
		 * @param type easing type to be use for easeIn calculation
		 * @param opt an object containing optional paramaters for back and elastic easing.  opt.os = overshoot (back), opt.a = amplitude (elastic), opt.p = period (elastic)
		 */
		public static function easeOut (s:Number,b:Number,e:Number,type:String,opt:Object):Array {
			if (type=="linear") return easeNone(s,b,e);
			
			//define easing functions
			var funcs:Array = getFuncs("easeOut");
			
			return doIteration(s,b,e,type,opt,funcs[type.toLowerCase()]);
		}
		
		/**
		 * easeInOut for all tween types
		 *
		 * @param s number of steps in the tween
		 * @param b begin value of tween
		 * @param e end value of tween
		 * @param type easing type to be use for easeIn calculation
		 * @param opt an object containing optional paramaters for back and elastic easing.  opt.os = overshoot (back), opt.a = amplitude (elastic), opt.p = period (elastic)
		 */
		public static function easeInOut (s:Number,b:Number,e:Number,type:String,opt:Object):Array {
			if (type=="linear") return easeNone(s,b,e);
			
			//define easing functions
			var funcs:Array = getFuncs("easeInOut");
			
			return doIteration(s,b,e,type,opt,funcs[type.toLowerCase()]);
		}
		
		public static function getCurrentTweenValue(propStart:Number,propDest:Number,timeStart:Number,timeNow:Number,timeDest:Number,easeType:String,opt:Object):Number {
			switch (easeType.toLowerCase()) {
				case "linear":
					return (propDest-propStart)*timeNow/(timeDest-timeStart)+propStart;
					break;
				case "easeinquad":
					return getFuncs("easein")["quad"](propDest-propStart,timeDest-timeStart,timeNow,propStart);
					break;
				case "easeoutquad":
					return getFuncs("easeout")["quad"](propDest-propStart,timeDest-timeStart,timeNow,propStart);
					break;
				case "easeinoutquad":
					return getFuncs("easeinout")["quad"](propDest-propStart,timeDest-timeStart,timeNow,propStart);
					break;
				case "easeincubic":
					return getFuncs("easein")["cubic"](propDest-propStart,timeDest-timeStart,timeNow,propStart);
					break;
				case "easeoutcubic":
					return getFuncs("easeout")["cubic"](propDest-propStart,timeDest-timeStart,timeNow,propStart);
					break;
				case "easeinoutcubic":
					return getFuncs("easeinout")["cubic"](propDest-propStart,timeDest-timeStart,timeNow,propStart);
					break;
				case "easeinquint":
					return getFuncs("easein")["quint"](propDest-propStart,timeDest-timeStart,timeNow,propStart);
					break;
				case "easeoutquint":
					return getFuncs("easeout")["quint"](propDest-propStart,timeDest-timeStart,timeNow,propStart);
					break;
				case "easeinoutquint":
					return getFuncs("easeinout")["quint"](propDest-propStart,timeDest-timeStart,timeNow,propStart);
					break;
				case "easeincirc":
					return getFuncs("easein")["circ"](propDest-propStart,timeDest-timeStart,timeNow,propStart);
					break;
				case "easeoutcirc":
					return getFuncs("easeout")["circ"](propDest-propStart,timeDest-timeStart,timeNow,propStart);
					break;
				case "easeinoutcirc":
					return getFuncs("easeinout")["circ"](propDest-propStart,timeDest-timeStart,timeNow,propStart);
					break;
				case "easeinquart":
					return getFuncs("easein")["quart"](propDest-propStart,timeDest-timeStart,timeNow,propStart);
					break;
				case "easeoutquart":
					return getFuncs("easeout")["quart"](propDest-propStart,timeDest-timeStart,timeNow,propStart);
					break;
				case "easeinoutquart":
					return getFuncs("easeinout")["quart"](propDest-propStart,timeDest-timeStart,timeNow,propStart);
					break;
				case "easeinexpo":
					return getFuncs("easein")["expo"](propDest-propStart,timeDest-timeStart,timeNow,propStart);
					break;
				case "easeoutexpo":
					return getFuncs("easeout")["expo"](propDest-propStart,timeDest-timeStart,timeNow,propStart);
					break;
				case "easeinoutexpo":
					return getFuncs("easeinout")["expo"](propDest-propStart,timeDest-timeStart,timeNow,propStart);
					break;
				case "easeinsine":
					return getFuncs("easein")["sine"](propDest-propStart,timeDest-timeStart,timeNow,propStart);
					break;
				case "easeoutsine":
					return getFuncs("easeout")["sine"](propDest-propStart,timeDest-timeStart,timeNow,propStart);
					break;
				case "easeinoutsine":
					return getFuncs("easeinout")["sine"](propDest-propStart,timeDest-timeStart,timeNow,propStart);
					break;
				case "easeinbounce":
					return getFuncs("easein")["bounce"](propDest-propStart,timeDest-timeStart,timeNow,propStart);
					break;
				case "easeoutbounce":
					return getFuncs("easeout")["bounce"](propDest-propStart,timeDest-timeStart,timeNow,propStart);
					break;
				case "easeinoutbounce":
					return getFuncs("easeinout")["bounce"](propDest-propStart,timeDest-timeStart,timeNow,propStart);
					break;
				case "easeinback":
					return getFuncs("easein")["back"](propDest-propStart,timeDest-timeStart,timeNow,propStart,opt.os);
					break;
				case "easeoutback":
					return getFuncs("easeout")["back"](propDest-propStart,timeDest-timeStart,timeNow,propStart,opt.os);
					break;
				case "easeinoutback":
					return getFuncs("easeinout")["back"](propDest-propStart,timeDest-timeStart,timeNow,propStart,opt.os);
					break;
				case "easeinelastic":
					return getFuncs("easein")["elastic"](propDest-propStart,timeDest-timeStart,timeNow,propStart,opt.a,opt.p);
					break;
				case "easeoutelastic":
					return getFuncs("easeout")["elastic"](propDest-propStart,timeDest-timeStart,timeNow,propStart,opt.a,opt.p);
					break;
				case "easeinoutelastic":
					return getFuncs("easeinout")["elastic"](propDest-propStart,timeDest-timeStart,timeNow,propStart,opt.a,opt.p);
					break;
				default:
					return 0;
					break;
			}
		}
		
		private static function bounceIn (c:Number,s:Number,i:Number,b:Number):Number { 
			return c-bounceOut(c,s,s-i,0)+b; 
		}
		
		private static function bounceOut (c:Number,s:Number,i:Number,b:Number):Number { 
			if ((i/=s)<(1/2.75)){ 
				return c*(7.5625*i*i)+b;
			} else if (i<(2/2.75)) { 
				return c*(7.5625*(i-=(1.5/2.75))*i+.75)+b;
			} else if (i<(2.5/2.75)) { 
				return c*(7.5625*(i-=(2.25/2.75))*i+.9375)+b;
			} else { 
				return c*(7.5625*(i-=(2.625/2.75))*i+.984375)+b; 
			} 
		}
		
		private static function doIteration(s:Number,b:Number,e:Number,type:String,opt:Object,func:Function):Array {
			var c:Number = e-b;
			//store the values
			var v:Array = new Array();
			//iterate through the steps to create the tween values
			for (var i:Number=0;i<=s;i++) {
				var t:String = type.toLowerCase();
				if (t=="back") v.push(func(c,s,i,b,opt.os));
				else if (t=="elastic") v.push(func(c,s,i,b,opt.a,opt.p));
				else v.push(func(c,s,i,b));
			}
			return v;
		}
		
		private static function getFuncs(type:String):Array {
			var funcs:Array;
			switch (type.toLowerCase()) {
				case "linear":
					funcs = new Array();
					return funcs;
					break;
				case "easein":
					funcs = new Array();
					funcs["quad"] = function(c:Number,s:Number,i:Number,b:Number):Number { return c*(i/=s)*i+b; };
					funcs["cubic"] = function(c:Number,s:Number,i:Number,b:Number):Number { return c*(i/=s)*i*i+b; };
					funcs["quint"] = function(c:Number,s:Number,i:Number,b:Number):Number { return c*(i/=s)*i*i*i*i+b; };
					funcs["circ"] = function(c:Number,s:Number,i:Number,b:Number):Number { return -c*(Math.sqrt(1-(i/=s)*i)-1)+b; };
					funcs["quart"] = function(c:Number,s:Number,i:Number,b:Number):Number { return c*(i/=s)*i*i*i+b; };
					funcs["expo"] = function(c:Number,s:Number,i:Number,b:Number):Number { return (i==0)?b:c*Math.pow(2,10*(i/s-1))+b; };
					funcs["sine"] = function(c:Number,s:Number,i:Number,b:Number):Number { return -c*Math.cos(i/s*(Math.PI/2))+c+b; };
					funcs["bounce"] = function(c:Number,s:Number,i:Number,b:Number):Number { return c-bounceOut(c,s,s-i,0)+b; };
					funcs["back"] = function(c:Number,s:Number,i:Number,b:Number,os:Number):Number { if (os==0) os=1.70158; return c*(i/=s)*i*((os+1)*i-os)+b; };
					funcs["elastic"] = function(c:Number,s:Number,i:Number,b:Number,a:Number,p:Number):Number { 
						var z:Number;
						if (i==0) return b;  
						if ((i/=s)==1) return b+c;  
						if (!p) p=s*.3;
						if (!a||a<Math.abs(c)) { a=c; z=p/4; }
						else z=p/(2*Math.PI)*Math.asin(c/a);
						return -(a*Math.pow(2,10*(i-=1))*Math.sin((i*s-z)*(2*Math.PI)/p))+b; 
					};
					return funcs;
					break;
				case "easeout":
					funcs = new Array();
					funcs["quad"] = function(c:Number,s:Number,i:Number,b:Number):Number { return -c*(i/=s)*(i-2)+b; };
					funcs["cubic"] = function(c:Number,s:Number,i:Number,b:Number):Number { return c*((i=i/s-1)*i*i+1)+b; };
					funcs["quint"] = function(c:Number,s:Number,i:Number,b:Number):Number { return c*((i=i/s-1)*i*i*i*i+1)+b; };
					funcs["circ"] = function(c:Number,s:Number,i:Number,b:Number):Number { return c*Math.sqrt(1-(i=i/s-1)*i)+b; };
					funcs["quart"] = function(c:Number,s:Number,i:Number,b:Number):Number { return -c*((i=i/s-1)*i*i*i-1)+b; };
					funcs["expo"] = function(c:Number,s:Number,i:Number,b:Number):Number { return (i==s)?b+c:c*(-Math.pow(2,-10*i/s)+1)+b; };
					funcs["sine"] = function(c:Number,s:Number,i:Number,b:Number):Number { return c*Math.sin(i/s*(Math.PI/2))+b; };
					funcs["bounce"] = function(c:Number,s:Number,i:Number,b:Number):Number { 
						if ((i/=s)<(1/2.75)){ return c*(7.5625*i*i)+b;
						} else if (i<(2/2.75)) { return c*(7.5625*(i-=(1.5/2.75))*i+.75)+b;
						} else if (i<(2.5/2.75)) { return c*(7.5625*(i-=(2.25/2.75))*i+.9375)+b;
						} else { return c*(7.5625*(i-=(2.625/2.75))*i+.984375)+b; } 
					};
					funcs["back"] = function(c:Number,s:Number,i:Number,b:Number,os:Number):Number { if (os==0) os=1.70158; return c*((i=i/s-1)*i*((os+1)*i+os)+1)+b; };
					funcs["elastic"] = function(c:Number,s:Number,i:Number,b:Number,a:Number,p:Number):Number { 
						var z:Number;
						if (i==0) return b;  
						if ((i/=s)==1) return b+c;  
						if (!p) p=s*.3;
						if (!a||a<Math.abs(c)) { a=c; z=p/4; }
						else z=p/(2*Math.PI)*Math.asin (c/a);
						return (a*Math.pow(2,-10*i)*Math.sin((i*s-z)*(2*Math.PI)/p)+c+b); 
					};
					return funcs;
					break;
				case "easeinout":
					funcs = new Array();
					funcs["quad"] = function(c:Number,s:Number,i:Number,b:Number):Number { if ((i/=s/2)<1) return c/2*i*i+b; return -c/2*((--i)*(i-2)-1)+b; };
					funcs["cubic"] = function(c:Number,s:Number,i:Number,b:Number):Number { if ((i/=s/2)<1) return c/2*i*i*i+b; return c/2*((i-=2)*i*i+2)+b; };
					funcs["quint"] = function(c:Number,s:Number,i:Number,b:Number):Number { if ((i/=s/2)<1) return c/2*i*i*i*i*i+b; return c/2*((i-=2)*i*i*i*i+2)+b; };
					funcs["circ"] = function(c:Number,s:Number,i:Number,b:Number):Number { if ((i/=s/2)<1) return -c/2*(Math.sqrt(1-i*i)-1)+b; return c/2*(Math.sqrt(1-(i-=2)*i)+1)+b; };
					funcs["quart"] = function(c:Number,s:Number,i:Number,b:Number):Number { if ((i/=s/2)<1) return c/2*i*i*i*i+b; return -c/2*((i-=2)*i*i*i-2)+b; };
					funcs["expo"] = function(c:Number,s:Number,i:Number,b:Number):Number { if (i==0) return b; if (i==s) return b+c; if ((i/=s/2)<1) return c/2*Math.pow(2,10*(i-1))+b; return c/2*(-Math.pow(2,-10*--i)+2)+b; };
					funcs["sine"] = function(c:Number,s:Number,i:Number,b:Number):Number { return -c/2*(Math.cos(Math.PI*i/s)-1)+b; };
					funcs["bounce"] = function(c:Number,s:Number,i:Number,b:Number):Number { if (i<s/2) return bounceIn(c,s,i*2,0)*.5+b; return bounceOut(c,s,i*2-s,0)*.5+c*.5+b; };
					funcs["back"] = function(c:Number,s:Number,i:Number,b:Number,os:Number):Number { if (os==0) os=1.70158; if ((i/=s/2)<1) return c/2*(i*i*(((os*=(1.525))+1)*i-os))+b; return c/2*((i-=2)*i*(((os*=(1.525))+1)*i+os)+2)+b; };
					funcs["elastic"] = function(c:Number,s:Number,i:Number,b:Number,a:Number,p:Number):Number { 
						var z:Number;
						if (i==0) return b;  
						if ((i/=s/2)==2) return b+c;  
						if (!p) p=s*(.3*1.5);
						if (!a||a<Math.abs(c)) { a=c; z=p/4; }
						else z=p/(2*Math.PI)*Math.asin(c/a);
						if (i<1) return -.5*(a*Math.pow(2,10*(i-=1))*Math.sin((i*s-z)*(2*Math.PI)/p))+b;
						return a*Math.pow(2,-10*(i-=1))*Math.sin((i*s-z)*(2*Math.PI)/p)*.5+c+b; 
					};
					return funcs;
					break;
				default:
					return null;
					break;
			}
		}		
	}
}